{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## CORDIC Testbench\n",
    "\n",
    "\n",
    "This notebook is to test the implementation of a CORDIC running on the programmable logic. The CORDIC is used to convert cartesian to polar coordinates. The output is compared with a Python calculation of the coordinate transform. It takes in x and y and gives out r and theta where r is the radius and theta is the angle."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/javascript": [
       "\n",
       "require(['notebook/js/codecell'], function(codecell) {\n",
       "  codecell.CodeCell.options_default.highlight_modes[\n",
       "      'magic_text/x-csrc'] = {'reg':[/^%%microblaze/]};\n",
       "  Jupyter.notebook.events.one('kernel_ready.Kernel', function(){\n",
       "      Jupyter.notebook.get_cells().map(function(cell){\n",
       "          if (cell.cell_type == 'code'){ cell.auto_highlight(); } }) ;\n",
       "  });\n",
       "});\n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from pynq import Overlay\n",
    "from pynq import MMIO\n",
    "import numpy as np\n",
    "import struct\n",
    "import binascii\n",
    "import cmath\n",
    "import random\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "NUM_SAMPLES = 50"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "ol=Overlay('./design_1_wrapper.bit') #Change name of bitstream as required"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "cordic_ip=MMIO(0x43C00000,0x10000) #Change base address as required"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "r_error=np.zeros(NUM_SAMPLES)\n",
    "theta_error=np.zeros(NUM_SAMPLES)\n",
    "ind=np.arange(NUM_SAMPLES)\n",
    "r_rmse=np.zeros(NUM_SAMPLES)\n",
    "theta_rmse=np.zeros(NUM_SAMPLES)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "ename": "error",
     "evalue": "unpack requires a buffer of 4 bytes",
     "output_type": "error",
     "traceback": [
      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[0;31merror\u001b[0m                                     Traceback (most recent call last)",
      "\u001b[0;32m<ipython-input-5-0c30105ce2eb>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m()\u001b[0m\n\u001b[1;32m     28\u001b[0m     \u001b[0;31m#Converting to float\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m     29\u001b[0m     \u001b[0;32mif\u001b[0m \u001b[0mr\u001b[0m\u001b[0;34m!=\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 30\u001b[0;31m         \u001b[0mr\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mstruct\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0munpack\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'>f'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mbinascii\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0munhexlify\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mr\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m     31\u001b[0m         \u001b[0mr\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mr\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m     32\u001b[0m     \u001b[0;32mif\u001b[0m \u001b[0mtheta\u001b[0m\u001b[0;34m!=\u001b[0m\u001b[0;36m0\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
      "\u001b[0;31merror\u001b[0m: unpack requires a buffer of 4 bytes"
     ]
    }
   ],
   "source": [
    "for i in range(NUM_SAMPLES):\n",
    "    #Generating random inputs\n",
    "    x=random.uniform(-1,1)\n",
    "    y=random.uniform(-1,1)\n",
    "    \n",
    "    #Computing golden output\n",
    "    cn=complex(x,y)\n",
    "    cn=cmath.polar(cn)\n",
    "    \n",
    "    #Converting input to bytes to be sent to FPGA\n",
    "    x=(struct.unpack('<I', struct.pack('<f', x))[0])\n",
    "    y=(struct.unpack('<I', struct.pack('<f', y))[0])\n",
    "    \n",
    "    #Writing values to the FPGA\n",
    "    cordic_ip.write(0x10,x)                             #Change the offset as mentioned in vivado file\n",
    "    cordic_ip.write(0x18,y)                             #Change the offset as mentioned in vivado file\n",
    "    \n",
    "    #Starting and stopping the IP (Don't change this)\n",
    "    cordic_ip.write(0x00,1)\n",
    "    cordic_ip.write(0x00,0)\n",
    "    \n",
    "    #Reading from IP\n",
    "    r=hex(cordic_ip.read(0x20))                         #Change the offset as mentioned in vivado file\n",
    "    r=r[2:]\n",
    "    theta=hex(cordic_ip.read(0x28))                     #Change the offset as mentioned in vivado file\n",
    "    theta=theta[2:]\n",
    "    \n",
    "    #Converting to float\n",
    "    if r!=0:\n",
    "        r=struct.unpack('>f', binascii.unhexlify(r))\n",
    "        r=r[0]\n",
    "    if theta!=0:\n",
    "        theta=struct.unpack('>f', binascii.unhexlify(theta))\n",
    "        theta=theta[0]\n",
    "        \n",
    "    #Comparing with golden output    \n",
    "    r_error[i]=\"{0:.6f}\".format(abs(r-cn[0]))\n",
    "    theta_error[i]=\"{0:.6f}\".format(abs(theta-cn[1]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Verifying Functionality"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sum_sq_r=0\n",
    "sum_sq_theta=0\n",
    "for i in range(NUM_SAMPLES):\n",
    "    sum_sq_r =sum_sq_r+(r_error[i]*r_error[i])\n",
    "    r_rmse = np.sqrt(sum_sq_r / (i+1))\n",
    "    sum_sq_theta =sum_sq_theta+(theta_error[i]*theta_error[i])\n",
    "    theta_rmse = np.sqrt(sum_sq_theta / (i+1))\n",
    "print(\"Radius RMSE: \", r_rmse, \"Theta RMSE:\", theta_rmse)    \n",
    "if r_rmse<0.001 and theta_rmse<0.001:\n",
    "    print(\"PASS\")\n",
    "else:\n",
    "    print(\"FAIL\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Displaying Errors"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.figure(figsize=(10, 5))\n",
    "plt.subplot(1,2,1)\n",
    "plt.bar(ind,r_error)\n",
    "plt.title(\"Radius Error\")\n",
    "plt.xlabel(\"Index\")\n",
    "plt.ylabel(\"Error\")\n",
    "#plt.xticks(ind)\n",
    "plt.tight_layout()\n",
    "\n",
    "plt.subplot(1,2,2)\n",
    "plt.bar(ind,theta_error)\n",
    "plt.title(\"Theta Error\")\n",
    "plt.xlabel(\"Index\")\n",
    "plt.ylabel(\"Error\")\n",
    "#plt.xticks(ind)\n",
    "plt.tight_layout()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
